#pragma once

/*
  Printable.h - Interface class that allows printing of complex types
  Copyright (c) 2011 Adrian McEwen.  All right reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

/*
 Print.cpp - Base class that provides print() and println()
 Copyright (c) 2008 David A. Mellis.  All right reserved.

 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version 2.1 of the License, or (at your option) any later version.

 This library is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public
 License along with this library; if not, write to the Free Software
 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

 Modified 23 November 2006 by David A. Mellis
 Modified 03 August 2015 by Chuck Todd
 */


#include <inttypes.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>

#include <type_traits>

#include "./pgm.hpp"
#include "./string.hpp"


namespace arduino_basic {

    class Print;

    /** The Printable class provides a way for new classes to allow themselves to be printed.
        By deriving from Printable and implementing the printTo method, it will then be possible
        for users to print out instances of this class by passing them into the usual
        Print::print and Print::println methods.
    */

    class Printable {
       public:
        virtual size_t printTo(Print &p) const = 0;
    };


    constexpr int DEC = 10;
    constexpr int HEX = 16;
    constexpr int OCT = 8;
    constexpr int BIN = 2;


    class Print {
       private:
        int write_error;

        size_t printNumber(unsigned long n, uint8_t base) {
            return write_unsigned_to_writer(*this, n, base);
        }

        size_t printFloat(double number, uint8_t digits) {
            return write_float_to_writer(*this, number, digits);
        }

       protected:
        void setWriteError(int err = 1) { write_error = err; }

       public:
        Print() :
            write_error(0) {}

        int getWriteError() { return write_error; }
        void clearWriteError() { setWriteError(0); }

        virtual size_t write(uint8_t) = 0;

        size_t write(const char *str) {
            size_t count = 0;

            while (*str) {
                write(*str);
                ++str;
            }

            return count;
        }

        virtual size_t write(const uint8_t *buffer, size_t size) {
            size_t n = 0;
            while (size--) {
                if (write(*buffer++))
                    n++;
                else
                    break;
            }
            return n;
        };

        size_t write(const char *buffer, size_t size) {
            return write((const uint8_t *)buffer, size);
        }

        // default to zero, meaning "a single write may block"
        // should be overriden by subclasses with buffering
        virtual int availableForWrite() { return 0; }

        virtual void flush() { /* Empty implementation for backward compatibility */ }

        size_t print(const __FlashStringHelper *ifsh) {
            PGM_P p = reinterpret_cast<PGM_P>(ifsh);
            size_t n = 0;
            while (1) {
                unsigned char c = pgm_read_byte(p++);
                if (c == 0) break;
                if (write(c))
                    n++;
                else
                    break;
            }
            return n;
        }

        size_t print(const String &s) {
            return write(s.c_str(), s.length());
        }

        size_t print(const char str[]) {
            return write(str);
        }

        size_t print(char c) {
            return write(c);
        }

        size_t print(unsigned char b, int base = DEC) {
            return print((unsigned long)b, base);
        };

        size_t print(int n, int base = DEC) {
            return print((long)n, base);
        }

        size_t print(unsigned int n, int base = DEC) {
            return print((unsigned long)n, base);
        };

        size_t print(long n, int base = DEC) {
            if (base == 0) {
                return write(n);
            }
            else if (base == 10) {
                if (n < 0) {
                    int t = print('-');
                    n = -n;
                    return printNumber(n, 10) + t;
                }
                return printNumber(n, 10);
            }
            else {
                return printNumber(n, base);
            }
        }

        size_t print(unsigned long n, int base = DEC) {
            if (base == 0)
                return write(n);
            else
                return printNumber(n, base);
        }

        size_t Print::print(double n, int digits = 2) {
            return printFloat(n, digits);
        };

        size_t print(const Printable &x) {
            return x.printTo(*this);
        }

        template <typename T>
        size_t println(T d) {
            using namespace std;
            size_t n = 0;
            n += print(d);
            n += println();
            return n;
        }

        template <typename T>
        size_t println(T num, int base_or_digits) {
            size_t n = print(num, base_or_digits);
            n += println();
            return n;
        }

        size_t println() {
            return write("\r\n");
        }

        int printf(const char *format, ...) __attribute__((format(printf, 2, 3)));

        int printf(const __FlashStringHelper *format, ...);
    };

}  // namespace arduino_basic